#include "stdafx.h"
#include "utils.h"

#if defined( WIN32)

void ScanDir( const std::string& absolutePath, const std::string& relativePath, const char* pattern, std::vector< FileInfo >& outFiles )
{
	// files
	std::vector< std::string > fileNames;
	{
		const std::string searchPath = absolutePath + pattern;

		WIN32_FIND_DATAA findData;
		ZeroMemory( &findData, sizeof( findData ) );
		HANDLE hFile = FindFirstFileA( searchPath.c_str(), &findData );
		if ( hFile != INVALID_HANDLE_VALUE )
		{
			do
			{
				if ( !( findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) )
				{
					fileNames.push_back( findData.cFileName );
				}
			} while ( FindNextFileA( hFile, &findData ) );
		}
	}

	// sort files (so this shit is deterministic)
	std::sort( fileNames.begin(), fileNames.end() );

	// emit files
	for ( const std::string& fileName : fileNames )
	{
		FileInfo info;
		info.m_relativePath = relativePath + fileName;
		info.m_absolutePath = absolutePath + fileName;
		outFiles.push_back( info );
	}

	// directories
	std::vector< std::string > directories;
	{
		const std::string searchPath = absolutePath + "*.";

		WIN32_FIND_DATAA findData;
		ZeroMemory( &findData, sizeof( findData ) );
		HANDLE hFile = FindFirstFileA( searchPath.c_str(), &findData );
		if ( hFile != INVALID_HANDLE_VALUE )
		{
			do
			{
				if ( findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
				{
					if ( findData.cFileName[0] != '.' )
					{
						directories.push_back( findData.cFileName );
					}
				}
			} while ( FindNextFileA( hFile, &findData ) );
		}
	}

	// sort directories (so this shit is deterministic)
	std::sort( directories.begin(), directories.end() );

	// recurse to sub directories
	for ( const std::string& dirName : directories )
	{
		const std::string newAbsolutePath = absolutePath + dirName + "\\";
		const std::string newRelativePath = relativePath + dirName + "\\";
		ScanDir( newAbsolutePath, newRelativePath, pattern, outFiles );
	}
}

bool GetFileModificationTime( const std::string& path, FileTime& outTimeCode )
{
	// get access to file
	HANDLE hFile = CreateFileA( path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL );
	if ( hFile == INVALID_HANDLE_VALUE )
		return false;

	// extract file time
	FILETIME creationTime, lastAccessTime, lastWriteTime;
	if ( !GetFileTime( hFile, &creationTime, &lastAccessTime, &lastWriteTime ) )
	{
		CloseHandle( hFile );
		return false;
	}

	// copy as Uint64
	outTimeCode = *( UINT64* )&lastWriteTime;
	CloseHandle( hFile );
	return true;
}

#elif defined( __linux__ )

#include <fnmatch.h>

void ScanDir( const std::string& absolutePath, const std::string& relativePath, const char* pattern, std::vector< FileInfo >& outFiles )
{
	// files
	std::vector< std::string > fileNames;
	{
		DIR* folder = opendir( absolutePath.c_str() );
		assert( folder != nullptr );
		
		struct dirent* entry;
		while ( ( entry = readdir( folder ) ) != nullptr )
		{
			// DT_DIR
			bool isFile = (entry->d_type == DT_REG );

			// if theres no support for d_type use stat
			if ( entry->d_type == DT_UNKNOWN )
			{
				// stat requires the full path
				const std::string fullFilename = absolutePath + entry->d_name;
				struct stat attr;
				stat( fullFilename.c_str(), &attr );
				if ( S_ISREG( attr.st_mode ) )
				{
					isFile = true;
				}
			}

			if ( isFile )
			{
				if ( fnmatch( pattern, entry->d_name, 0 ) == 0 )
				{
					fileNames.push_back( entry->d_name );
				}
			}
		}
		closedir( folder );
	}

	// sort files (so this shit is deterministic)
	std::sort( fileNames.begin(), fileNames.end() );

	// emit files
	for ( const std::string& fileName : fileNames )
	{
		FileInfo info;
		info.m_relativePath = relativePath + fileName;
		info.m_absolutePath = absolutePath + fileName;
		outFiles.push_back( info );
	}

	// directories
	std::vector< std::string > directories;
	{
		DIR* folder = opendir( absolutePath.c_str() );
		assert( folder != nullptr );

		struct dirent* entry;
		while ( ( entry = readdir( folder ) ) != nullptr )
		{
			bool isDir = ( entry->d_type == DT_DIR );

			// if theres no support for d_type use stat
			if ( entry->d_type == DT_UNKNOWN )
			{
				// stat requires the full path
				const std::string fullDirectory = absolutePath + entry->d_name;
				struct stat attr;
				stat( fullDirectory.c_str(), &attr );
				if ( S_ISDIR( attr.st_mode ) )
				{
					isDir = true;
				}
			}

			if ( isDir )
			{
				if ( entry->d_name[0] != '.' )
				{
					directories.push_back( entry->d_name );
				}
			}
		}
		closedir( folder );
	}

	// sort directories (so this shit is deterministic)
	std::sort( directories.begin(), directories.end() );

	// recurse to sub directories
	for ( const std::string& dirName : directories )
	{
		const std::string newAbsolutePath = absolutePath + dirName + "/";
		const std::string newRelativePath = relativePath + dirName + "/";

		ScanDir( newAbsolutePath, newRelativePath, pattern, outFiles );
	}
}

bool GetFileModificationTime( const std::string& path, FileTime& outTimeCode )
{
	struct stat attr;
	if ( stat( path.c_str(), &attr ) == 0 )
	{
		outTimeCode = attr.st_mtime;
		return true;
	}
	return false;
}

#else
#	error Platform not supported!
#endif

std::string LoadStringFromFile( const std::string& path )
{
	// get access to file
	std::ifstream hFile( path, std::ios::in | std::ios::binary );
	if ( !hFile )
		return "";

	// get file size
	hFile.seekg( 0, std::ios::end );
	size_t fileSize = hFile.tellg();
	hFile.seekg( 0, std::ios::beg );

	// allocate string
	std::string ret;
	ret.resize( fileSize, ' ' );

	// load data
	hFile.read( &ret[0], fileSize );

	return ret;
}

const char* LastStr( const char* txt, const char* pat )
{
	const char* found = ::strstr( txt, pat );
	while ( found )
	{
		const char* next = ::strstr( found + 1, pat );
		if ( !next )
			break;

		found = next;
	}

	return found;
}

std::string ShortRelativePath( const std::string& path )
{
	const char* str = LastStr( path.c_str(), "include\\" );
	if ( !str )
	{
		str = LastStr( path.c_str(), "src\\" );
		if ( !str )
			return "";

		return str + 4;
	}
	else
	{
		return str + 8;
	}
}

std::string BasePath( const std::string& path )
{
	const char* str = LastStr( path.c_str(), "include\\" );
	if ( str != nullptr )
	{
		const int len = str - path.c_str();
		return std::string( path.c_str(), len );
	}

	str = LastStr( path.c_str(), "src\\" );
	if ( str != nullptr )
	{
		const int len = str - path.c_str();
		return std::string( path.c_str(), len );
	}

	return "";
}